package momo.app.structure;

import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import momo.app.comms.RcvChannel;
import momo.app.comms.SendChannel;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;


public class RelationshipContainer
{
	private Log log = LogFactory.getLog(RelationshipContainer.class);
	private Log logStructure = LogFactory.getLog("MoMoStructureLog");
	private Map<String, Relationship> rsMap;
	private boolean hasParent;
	private int noChildren;
	
	private String parentId;
	private HashSet<String> childrenId;
	
	public RelationshipContainer()
	{
		rsMap = Collections.synchronizedMap(new HashMap<String, Relationship>());
		hasParent = false;
		noChildren = 0;
		
		parentId = "";
		childrenId = new HashSet<String>();
	}
	
	synchronized public SendChannel getParentChannel()
	{
		if ( hasParent() )
		{
			Iterator<String> keyIter = rsMap.keySet().iterator();
			while ( keyIter.hasNext() )
			{
				Relationship rs = rsMap.get( keyIter.next() );
				if ( rs.isParent() )
					return rs.getChannels().getSendChannel();
			}
		}
		return null;
	}
	
	synchronized public Set<SendChannel> getChildrenChannel()
	{
		Set<SendChannel> setToRet = new HashSet<SendChannel>();
		
		if ( hasChildren() )
		{
			Iterator<String> keyIter = rsMap.keySet().iterator();
			while ( keyIter.hasNext() )
			{
				Relationship rs = rsMap.get( keyIter.next() );
				if ( rs.isParent() == false )
					setToRet.add( rs.getChannels().getSendChannel() );
			}
		}
		return Collections.unmodifiableSet( setToRet );
	}
	
	synchronized public boolean addParent(Peer peer, RcvChannel rC, SendChannel sC)
	{
		if ( hasParent )
		{
			if ( log.isDebugEnabled() )
				log.debug("addParent fails. Parent Id: " + peer.getId());
			if ( logStructure.isDebugEnabled() )
				logStructure.debug("addParent fails. Parent Id: " + peer.getId());
			return false;
		}else
		{
			hasParent = true;
			parentId = peer.getId();
			put(peer, rC, sC, hasParent);
			
			if ( log.isDebugEnabled() )
				log.debug("Parent added ID: " + peer.getId() + ", rcv: " + rC.getSenderPort() + ", send: " + sC.getMyPort());
			if ( logStructure.isDebugEnabled() )
				logStructure.debug("Parent added ID: " + peer.getId() + ", rcv: " + rC.getSenderPort() + ", send: " + sC.getMyPort());
			
			this.display();
			return true;
		}
	}
	
	synchronized public void addChildren(Peer peer, RcvChannel rC, SendChannel sC)
	{
		String peerId = peer.getId();
		Relationship rs = rsMap.get( peerId );
		if ( rs == null )
			noChildren ++;
		childrenId.add( peerId );
		put(peer, rC, sC, false);
		
		if ( log.isDebugEnabled() )
			log.debug("Child added ID: " + peer.getId() + ", rcv: " + rC.getSenderPort() + ", send: " + sC.getMyPort());
		if ( logStructure.isDebugEnabled() )
			logStructure.debug("Child added ID: " + peer.getId() + ", rcv: " + rC.getSenderPort() + ", send: " + sC.getMyPort());
		
		this.display();
	}
	
	synchronized private void put(Peer peer, RcvChannel rC, SendChannel sC, boolean isParent)
	{
		Relationship rs = new Relationship(peer, rC, sC, isParent);
		rsMap.put(peer.getId(), rs);
	}
	
	synchronized private void put(Peer peer, ChannelLink channels, boolean isParent)
	{
		Relationship rs = new Relationship(peer, channels, isParent);
		rsMap.put(peer.getId(), rs);
	}
	
	synchronized private boolean add(Peer peer, RcvChannel rcv, SendChannel send, boolean isParent)
	{
		Relationship rs = rsMap.get(peer.getId());
		if ( rs == null )
		{
			this.put(peer, rcv, send, isParent);
			return true;
		}
		return false;
	}
	
	synchronized private boolean add(Peer peer, ChannelLink channels, boolean isParent)
	{
		Relationship rs = rsMap.get(peer.getId());
		if ( rs == null )
		{
			this.put(peer, channels, isParent);
			return true;
		}
		return false;
	}
	
	synchronized public Relationship removeParent()
	{
		if ( hasParent )
			return this.remove(parentId);
		return null;
	}
	
	synchronized public Relationship remove(String id)
	{
		Relationship rs = rsMap.remove(id);
		if ( rs != null )
		{
			if ( rs.isParent() )
			{
				hasParent = false;
				parentId = "";
				if ( log.isDebugEnabled() )
					log.debug("Parent: " + id + " is being removed");
				if ( logStructure.isDebugEnabled() )
					logStructure.debug("Parent: " + id + " is being removed");
			}else
			{
				noChildren --;
				childrenId.remove(id);
				if ( log.isDebugEnabled() )
					log.debug("Child: " + id + " is being removed");
				if ( logStructure.isDebugEnabled() )
					logStructure.debug("Child: " + id + " is being removed");
			}
		}
		else {
			if ( log.isErrorEnabled() )
				log.error("Relationship is null for id:"+id);
			if ( logStructure.isErrorEnabled() )
				logStructure.error("Relationship is null for id:"+id);
			throw new IllegalArgumentException("Relationship is null for id: ");
		}
		return rs;
	}
	
	synchronized public Relationship remove(String id, RcvChannel rC)
	{
		Relationship rs = rsMap.get(id);
		if ( rs != null )
		{
			RcvChannel channel = rs.getChannels().getRcvChannel();
			if ( channel == rC )
			{
				if ( rs.isParent() )
				{
					hasParent = false;
					parentId = "";
					if ( log.isDebugEnabled() )
						log.debug("Parent: " + id + " is being removed");
					if ( logStructure.isDebugEnabled() )
						logStructure.debug("Parent: " + id + " is being removed");
				}else
				{
					noChildren --;
					childrenId.remove(id);
					if ( log.isDebugEnabled() )
						log.debug("Children: " + id + " is being removed");
					if ( logStructure.isDebugEnabled() )
						logStructure.debug("Children: " + id + " is being removed");
				}
				
				return rsMap.remove(id);
			}
		}
		return null;
	}
	
	synchronized public boolean hasParent()
	{
		return hasParent;
	}
	
	synchronized public boolean hasChildren()
	{
		return noChildren>0?true:false; 
	}
	
	synchronized public boolean isParent(String id)
	{
		if ( hasParent )
			return parentId.equals(id);
		return false;
	}
	
	synchronized public boolean isChild(String id)
	{
		if ( hasChildren() )
			return childrenId.contains(id);
		return false;
	}
	
	synchronized public int getNoChildren()
	{
		return noChildren;
	}
	
	synchronized public void clear()
	{
		Iterator<String> keysIter = rsMap.keySet().iterator();
		while ( keysIter.hasNext() )
		{
			Relationship rs = rsMap.get(keysIter.next());
//			rs.getChannels().closeChannels();
			rs.getChannels().getRcvChannel().shutdownNow();
			rs.getChannels().getSendChannel().shutdownNow();
		}
		rsMap.clear();
		hasParent = false;
		parentId = "";
		childrenId.clear();
		noChildren = 0;
	}
	
	/**
	 * Test method. Used to display all the channels
	 * @param callFrom
	 */
	synchronized public void display()
	{
		StringBuffer buffer = new StringBuffer();
		Iterator<String> keyIter =rsMap.keySet().iterator();
		while ( keyIter.hasNext() )
		{
			Relationship rs = rsMap.get( keyIter.next() );
			String id = rs.getPeer().getId();
			boolean isParent = rs.isParent();
			if ( isParent )
				buffer.insert(0, "\t - Parent: " + id + "\n");
			else
				buffer.append("\t - Child: " + id + "\n");
		}
		buffer.insert(0, "Display:\n");
		if ( log.isDebugEnabled() )
			log.debug(buffer.toString());
		if ( logStructure.isDebugEnabled() )
			logStructure.debug(buffer.toString());
	}
	
}//end of class Relationship
